package maths

import (
	"fmt"
	"golang.org/x/exp/constraints"
	"math"
	"strconv"
	"strings"
)

// Exponent returns x^n
// n must be non-negative.
// 返回x的n次方
func Exponent(x, n int64) int64 {
	if n == 0 {
		return 1
	}

	t := Exponent(x, n/2)
	if n%2 == 1 {
		return t * t * x
	}

	return t * t
}

// FibonacciNum returns the nth Fibonacci number
// 斐波那契数列
// n 从0开始
// 返回第n个斐波那契数
func FibonacciNum(first, second, n int) int {
	if n <= 0 {
		return 0
	}

	if n < 3 {
		return 1
	}

	if n == 3 {
		return first + second
	}
	return FibonacciNum(second, first+second, n-1)
}

// Factorial 计算x的阶乘
// x 必须是正整数
// 返回x的阶乘
func Factorial(x uint) uint {
	var f uint = 1
	for ; x > 1; x-- {
		f *= x
	}
	return f
}

// Percent 计算百分比
// val: 值
// total: 总数
// n: 保留小数位数
// 返回百分比
func Percent(val, total float64, n int) float64 {
	if total == 0 {
		return float64(0)
	}
	tmp := val / total * 100
	return RoundToFloat(tmp, n)
}

// RoundToString 浮点数四舍五入
// x: 浮点数
// n: 保留小数位数
// 返回四舍五入后的字符串
func RoundToString[T constraints.Float | constraints.Integer](x T, n int) string {
	tmp := math.Pow(10.0, float64(n))
	x *= T(tmp)
	r := math.Round(float64(x))
	return strconv.FormatFloat(r/tmp, 'f', n, 64)
}

// RoundToFloat 浮点数四舍五入
// x: 浮点数
// n: 保留小数位数
// 返回四舍五入后的浮点数
func RoundToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
	tmp := math.Pow(10.0, float64(n))
	x *= T(tmp)
	r := math.Round(float64(x))
	return r / tmp
}

// TruncRound 截断小数点，保留n位小数
// x: 浮点数
// n: 保留小数位数
// 返回截断后的浮点数
func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T {
	floatStr := fmt.Sprintf("%."+strconv.Itoa(n+1)+"f", x)
	temp := strings.Split(floatStr, ".")
	var newFloat string
	if len(temp) < 2 || n >= len(temp[1]) {
		newFloat = floatStr
	} else {
		newFloat = temp[0] + "." + temp[1][:n]
	}
	result, _ := strconv.ParseFloat(newFloat, 64)
	return T(result)
}

// FloorToFloat 截断小数点，保留n位小数
// x: 浮点数
// n: 保留小数位数
// 返回截断后的浮点数
func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
	tmp := math.Pow(10.0, float64(n))
	x *= T(tmp)
	r := math.Floor(float64(x))
	return r / tmp
}

// FloorToString 截断小数点，保留n位小数
// x: 浮点数
// n: 保留小数位数
// 返回截断后的字符串
func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string {
	tmp := math.Pow(10.0, float64(n))
	x *= T(tmp)
	r := math.Floor(float64(x))
	result := strconv.FormatFloat(r/tmp, 'f', n, 64)
	return result
}

// CeilToFloat 向上取整
// x: 浮点数
// n: 保留小数位数
// 返回向上取整后的浮点数
func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
	tmp := math.Pow(10.0, float64(n))
	x *= T(tmp)
	r := math.Ceil(float64(x))
	return r / tmp
}

// CeilToString 向上取整
// x: 浮点数
// n: 保留小数位数
// 返回向上取整后的字符串
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string {
	tmp := math.Pow(10.0, float64(n))
	x *= T(tmp)
	r := math.Ceil(float64(x))
	result := strconv.FormatFloat(r/tmp, 'f', n, 64)
	return result
}

// Max 返回最大值
// 返回最大值
// 参数必须为数字类型
func Max[T constraints.Integer | constraints.Float](numbers ...T) T {
	tmpMax := numbers[0]
	for _, v := range numbers {
		if tmpMax < v {
			tmpMax = v
		}
	}
	return tmpMax
}

// MaxBy 返回最大值
// 返回最大值
func MaxBy[T any](slice []T, comparator func(T, T) bool) T {
	var tmpMax T

	if len(slice) == 0 {
		return tmpMax
	}
	tmpMax = slice[0]
	for i := 1; i < len(slice); i++ {
		val := slice[i]

		if comparator(val, tmpMax) {
			tmpMax = val
		}
	}
	return tmpMax
}

// Min 返回最小值
// 返回最小值
func Min[T constraints.Integer | constraints.Float](numbers ...T) T {
	tmpMax := numbers[0]
	for _, v := range numbers {
		if tmpMax > v {
			tmpMax = v
		}
	}
	return tmpMax
}

// MinBy 返回最小值
// 返回最小值
func MinBy[T any](slice []T, comparator func(T, T) bool) T {
	var minTmp T
	if len(slice) == 0 {
		return minTmp
	}
	minTmp = slice[0]
	for i := 1; i < len(slice); i++ {
		val := slice[i]
		if comparator(val, minTmp) {
			minTmp = val
		}
	}
	return minTmp
}

// Sum 求和
// 返回和
func Sum[T constraints.Integer | constraints.Float](numbers ...T) T {
	var sum T
	for _, v := range numbers {
		sum += v
	}
	return sum
}

// Average 平均值
// 返回平均值
func Average[T constraints.Integer | constraints.Float](numbers ...T) T {
	var sum T
	n := T(len(numbers))
	for _, v := range numbers {
		sum += v
	}
	return sum / n
}

// ContinuousNumber 生成连续的数字数组
// start: 起始值
// count: 数量
// 返回连续的数字数组
func ContinuousNumber[T constraints.Integer | constraints.Float](start T, count int) []T {
	size := count
	if count < 0 {
		size = -count
	}
	result := make([]T, size)
	for i, j := 0, start; i < size; i, j = i+1, j+1 {
		result[i] = j
	}
	return result
}

// RangeWithStep 生成连续的数字数组，步长为step
// start: 起始值
// end: 结束值
// step: 步长
func RangeWithStep[T constraints.Integer | constraints.Float](start, end, step T) []T {
	result := []T{}
	if start >= end || step == 0 {
		return result
	}
	for i := start; i < end; i += step {
		result = append(result, i)
	}
	return result
}

// Div 除法
// 返回除法结果
func Div[T constraints.Float | constraints.Integer](x T, y T) float64 {
	return float64(x) / float64(y)
}

// Abs 绝对值
// 返回绝对值
func Abs[T constraints.Integer | constraints.Float](x T) T {
	if x < 0 {
		return -x
	}
	return x
}

// Log 自然对数
// n: 数值
// 返回自然对数
func Log(n, base float64) float64 {
	return math.Log(n) / math.Log(base)
}

// Sin 正弦
// precision: 精度
// radian: 弧度
// 返回正弦
func Sin(radian float64, precision ...int) float64 {
	return Cos((math.Pi/2)-radian, precision...)
}

// Cos 余弦
// precision: 精度
// radian: 弧度
// 返回余弦
func Cos(radian float64, precision ...int) float64 {
	t := 1.0 / (2.0 * math.Pi)
	radian *= t
	radian -= 0.25 + math.Floor(radian+0.25)
	radian *= 16.0 * (math.Abs(radian) - 0.5)
	radian += 0.225 * radian * (math.Abs(radian) - 1.0)
	if len(precision) == 1 {
		return TruncRound(radian, precision[0])
	}
	return TruncRound(radian, 3)
}

// GCD 最大公约数
// 返回最大公约数
func GCD[T constraints.Integer](integers ...T) T {
	result := integers[0]
	for k := range integers {
		result = gcd(integers[k], result)
		if result == 1 {
			return 1
		}
	}
	return result
}

// AngleToRadian 角度转弧度
// angle: 角度
// 返回弧度
func AngleToRadian(angle float64) float64 {
	radian := angle * (math.Pi / 180)
	return radian
}

// RadianToAngle 弧度转角度
// radian: 弧度
// 返回角度
func RadianToAngle(radian float64) float64 {
	angle := radian * (180 / math.Pi)
	return angle
}

// PointDistance 点到直线的距离
// x1: 点1的横坐标
// y1: 点1的纵坐标
// x2: 点2的横坐标
// y2: 点2的纵坐标
// 返回点到直线的距离
func PointDistance(x1, y1, x2, y2 float64) float64 {
	a := x1 - x2
	b := y1 - y2
	c := math.Pow(a, 2) + math.Pow(b, 2)
	return math.Sqrt(c)
}

// IsPrime 判断是否为质数
// n: 整数
// 返回是否为质数
func IsPrime(n int) bool {
	if n < 2 {
		return false
	}
	for i := 2; i <= int(math.Sqrt(float64(n))); i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

// LCM 最小公倍数
// 返回最小公倍数
func LCM[T constraints.Integer](integers ...T) T {
	result := integers[0]
	for k := range integers {
		result = lcm(integers[k], result)
	}
	return result
}

// gcd 递归求最大公约数
func gcd[T constraints.Integer](a, b T) T {
	if b == 0 {
		return a
	}
	return gcd[T](b, a%b)
}

// lcm 递归求最小公倍数
func lcm[T constraints.Integer](a, b T) T {
	if a == 0 || b == 0 {
		panic("lcm function: provide non zero integers only.")
	}
	return a * b / gcd(a, b)
}
